#include "parser.h"

static char *token_begin[] = {
    "duration ",
    "file ",
    "tag album ",
    "tag albumartist ",
    "tag artist ",
    "tag comment ",
    "tag date ",
    "tag genre ",
    "tag title ",
    "stream ",
    "tag discnumber ",
    "tag cr_tracknumber ",
    "set aaa_mode ",
    "status ",
    "set vol_left ",
    "set vol_right ",
    "position ",
    "set repeat ",
    "set repeat_current ",
    "set shuffle "
};

static char *line_end(char *str) {
    char *p;
    if ((p = strchr(str, '\n')) != NULL)
        return p;
    return (p = strchr(str, '\0'));
}

static char *substr(char *begin, char *end) {
    size_t len = end - begin;
    if (len <= 0)
        return NULL;
    char *substr = malloc(len + 1);
    memset(substr, 0, len + 1);
    strncpy(substr, begin, len);
    assert(substr[len] == '\0');
    return substr;
}

static bool reassign_uint64_if_changed(char *new_str, uint64_t *old) {
    /* as per documentation, reset errno for strtoul */
    errno = 0;
    uint64_t new = strtoul(new_str, NULL, 0);

    /* check for conversion errors */
    if (new == 0 && (errno == EINVAL || errno == ERANGE))
        fprintf(stderr, "error: error converting uint string: %s\n", new_str);

    /* only change what changed */
    bool changed = *old != new;
    if (changed)
        *old = new;
    return changed;
}

static bool reassign_str_if_changed(char *new, char **old) {
    bool changed = (*old == NULL || strcmp(new, *old) != 0);
    if (changed) {
        if (DEBUG)
            printf("str changed %s\n", new);
        free(*old);
        *old = strdup(new);
    }
    return changed;
}

static bool reset_str(char **old) {
    bool changed = (*old != NULL);
    if (changed) {
        if (DEBUG)
            printf("str cleared %s\n", *old);
        free(*old);
        *old = NULL;
    }
    return changed;
}

void fill_cr_track_or_cr_player(bool *update, cr_track *track, cr_player *player, char *buf, int buf_len, bool *incomplete_parse) {
    /* todo: more sophisticated parsing and handling */
    if (*incomplete_parse) {
        fprintf(stderr, "error: incomplete parse detected!\n");
        *incomplete_parse = false;
    }

    bool processed = false;
    for (int i = 0; i < TOKEN_ENUM_SIZE; i++) {
        /* prevent unnecessary iterations */
        if (processed)
            break;

        /* search for token sequence */
        char *begin_p = strstr(buf, token_begin[i]);
        if (begin_p == NULL)
            continue;
        /* skip the begin sequences itself */
        begin_p += strlen(token_begin[i]);
        /* search for end, beginning at begin_p */
        char *end_p = line_end(begin_p);

        char *part = substr(begin_p, end_p);
        if (part == NULL)
            continue;

        /* if already scheduled for an update */
        if (update[i])
            continue;

        /* oh boy, here we go */
        switch (i) {
            case TOKEN_DURATION: {
                update[TOKEN_DURATION] = reassign_uint64_if_changed(part, &track->duration);
                break;
            }

            case TOKEN_FILE: {
                update[TOKEN_FILE] = reassign_str_if_changed(part, &track->file);
                if (update[TOKEN_FILE]) {
                    /* these fields have to be cleared for streams  */
                    /* naturally, more fields would have to be cleared, but */
                    /* these are the ones that are currently being drawn */
                    update[TOKEN_TITLE_TAG] = reset_str(&track->title_tag);
                    update[TOKEN_ALBUM_TAG] = reset_str(&track->album_tag);
                    update[TOKEN_ARTIST_TAG] = reset_str(&track->artist_tag);
                    /* the same has to be done in reverse */
                    update[TOKEN_STREAM] = reset_str(&track->stream);
                }
                break;
            }

            case TOKEN_ALBUM_TAG: {
                update[TOKEN_ALBUM_TAG] = reassign_str_if_changed(part, &track->album_tag);
                break;
            }

            case TOKEN_ALBUMARTIST_TAG: {
                update[TOKEN_ALBUMARTIST_TAG] = reassign_str_if_changed(part, &track->albumartist_tag);
                break;
            }

            case TOKEN_ARTIST_TAG: {
                update[TOKEN_ARTIST_TAG] = reassign_str_if_changed(part, &track->artist_tag);
                break;
            }

            case TOKEN_COMMENT_TAG: {
                update[TOKEN_COMMENT_TAG] = reassign_str_if_changed(part, &track->comment_tag);
                break;
            }

            case TOKEN_DATE_TAG: {
                update[TOKEN_DATE_TAG] = reassign_str_if_changed(part, &track->date_tag);
                break;
            }

            case TOKEN_GENRE_TAG: {
                update[TOKEN_GENRE_TAG] = reassign_str_if_changed(part, &track->genre_tag);
                break;
            }

            case TOKEN_TITLE_TAG: {
                update[TOKEN_TITLE_TAG] = reassign_str_if_changed(part, &track->title_tag);
                break;
            }

            case TOKEN_STREAM: {
                update[TOKEN_STREAM] = reassign_str_if_changed(part, &track->stream);
                break;
            }

            case TOKEN_DISCNUMBER_TAG: {
                update[TOKEN_DISCNUMBER_TAG] = reassign_uint64_if_changed(part, &track->discnumber_tag);
                break;
            }

            case TOKEN_TRACKNUMBER_TAG: {
                update[TOKEN_TRACKNUMBER_TAG] = reassign_uint64_if_changed(part, &track->cr_tracknumber_tag);
                break;
            }

            case TOKEN_AAA_MODE: {
                update[TOKEN_AAA_MODE] = reassign_str_if_changed(part, &player->aaa_mode);
                break;
            }

            case TOKEN_STATUS: {
                update[TOKEN_STATUS] = reassign_str_if_changed(part, &player->status);
                break;
            }

            case TOKEN_VOL_LEFT: {
                update[TOKEN_VOL_LEFT] = reassign_uint64_if_changed(part, &player->vol_left);
                break;
            }

            case TOKEN_VOL_RIGHT: {
                update[TOKEN_VOL_RIGHT] = reassign_uint64_if_changed(part, &player->vol_right);
                break;
            }

            case TOKEN_POSITION: {
                update[TOKEN_POSITION] = reassign_uint64_if_changed(part, &player->position);
                break;
            }

            case TOKEN_REPEAT: {
                bool old = player->repeat;
                player->repeat = strstr(part, "true") != NULL;
                update[TOKEN_REPEAT] = player->repeat != old;
                break;
            }

            case TOKEN_REPEAT_CURRENT: {
                bool old = player->repeat_current;
                player->repeat_current = strstr(part, "true") != NULL;
                update[TOKEN_REPEAT_CURRENT] = player->repeat_current != old;
                break;
            }

            case TOKEN_SHUFFLE: {
                bool old = player->shuffle;
                player->shuffle = strstr(part, "true") != NULL;
                update[TOKEN_SHUFFLE] = player->shuffle != old;
                break;
            }

            default:
                fprintf(stderr, "error: default case during parsing reached!\n");
                break;

            if (*end_p == '\0')
                *incomplete_parse = true;
        }

        /* if current entry was updated */
        if (update[i])
            processed = true;

        /* free previously assigned part */
        free(part);
    }
}